feat: add intel arc gpu auto-detection via xpu-smi#37
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
Walkthrough
ChangesIntel XPU GPU detection
Sequence Diagram(s)sequenceDiagram
participant hardware.detect_local_gpu() as DetectLocalGpu
participant _detect_nvidia_gpu() as NvidiaHelper
participant _detect_amd_gpu() as AmdHelper
participant _detect_intel_gpu() as IntelHelper
participant _detect_apple_gpu() as AppleHelper
participant xpu-smi discovery as XpuSmi
DetectLocalGpu->>NvidiaHelper: try NVIDIA detection
DetectLocalGpu->>AmdHelper: try AMD detection
DetectLocalGpu->>IntelHelper: run xpu-smi discovery
IntelHelper->>XpuSmi: execute discovery
XpuSmi-->>IntelHelper: device name and memory physical size lines
IntelHelper-->>DetectLocalGpu: Intel label, VRAM, GPU count
DetectLocalGpu->>AppleHelper: try Apple Silicon fallback
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Up to standards ✅🟢 Issues
|
| Metric | Results |
|---|---|
| Complexity | 42 |
| Duplication | 0 |
AI Reviewer: first review requested successfully. AI can make mistakes. Always validate suggestions.
TIP This summary will be updated as you push new changes.
There was a problem hiding this comment.
Pull Request Overview
The PR is currently not up to standards according to Codacy analysis. While it successfully implements Intel GPU auto-detection via xpu-smi, it significantly increases the technical debt in src/modelinfo/hardware.py, which has reached a cyclomatic complexity of 24.
A critical logic issue was identified in the parsing routine: if GPU names are detected but memory parsing fails (leaving the total at 0.0), the function will return an invalid result instead of falling back to other detection methods. Additionally, while the implementation provides unit conversion for various memory formats (GiB, KiB, B), these paths are currently untested. These issues, alongside the high complexity of the 'God Function' for hardware detection, should be addressed before merging.
About this PR
- The unit conversion logic for non-MiB units (GiB, KiB, and Bytes) is currently untested. Given the critical nature of hardware reporting, please add unit tests covering these specific unit strings to ensure the math and regex parsing behave as expected.
1 comment outside of the diff
src/modelinfo/hardware.py
line 160🟡 MEDIUM RISK
Suggestion: The detect_local_gpu function is becoming a 'God Function' for hardware detection. Refactoring this into provider-specific helpers (e.g., _detect_intel_gpu) would improve maintainability and allow for easier testing of individual detection paths.Try running the following prompt in your IDE agent:
Refactor the detect_local_gpu function in src/modelinfo/hardware.py by extracting the specific detection logic for NVIDIA (lines 161-191), AMD (lines 193-219), Intel (lines 221-264), and Apple (lines 266-280) into separate private functions. Each private function should return a tuple of (name, vram, count) or None if the respective hardware is not detected.
Test suggestions
- Detection of a single Intel Arc GPU with MiB to GiB VRAM conversion
- Detection and aggregation of multiple Intel Data Center GPUs
- Verification of unit conversion logic for GiB, KiB, and Byte inputs
- Fallback to Apple Silicon detection logic when xpu-smi command fails
Prompt proposal for missing tests
Consider implementing these tests if applicable:
1. Verification of unit conversion logic for GiB, KiB, and Byte inputs
TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback
| if gpu_names: | ||
| gpu_count = len(gpu_names) | ||
| first_name = gpu_names[0] | ||
| display_name = ( | ||
| f"Intel Multi-GPU ({gpu_count}x {first_name})" if gpu_count > 1 else first_name | ||
| ) | ||
| return display_name, total_mib / 1024.0, gpu_count |
There was a problem hiding this comment.
🟡 MEDIUM RISK
Suggestion: The function should only return if both GPU names and a non-zero memory value were successfully parsed. This ensures that a failed parse correctly falls back to subsequent detection methods or the final 'Unknown' default.
Try running the following prompt in your coding agent:
Refactor the xpu-smi parsing logic in detect_local_gpu to be more robust. Ensure the function only returns if it successfully parsed both a name and a non-zero memory value, allowing fallback to continue if parsing fails. Also, simplify the unit parsing by combining the regex searches.
| match = re.search(r"([\d\.]+)", size_str) | ||
| if match: | ||
| val = float(match.group(1)) | ||
| unit_match = re.search(r"([a-zA-Z]+)", size_str) | ||
| if unit_match: | ||
| unit = unit_match.group(1).lower() | ||
| if unit in ("gib", "gb"): | ||
| val *= 1024.0 | ||
| elif unit in ("kib", "kb"): | ||
| val /= 1024.0 | ||
| elif unit == "b": | ||
| val /= (1024.0 * 1024.0) | ||
| total_mib += val |
There was a problem hiding this comment.
⚪ LOW RISK
Suggestion: Simplify the extraction of value and unit using a single regex with groups. This makes the parsing logic cleaner and more explicit.
match = re.search(r"([\d\.]+)\s*([a-zA-Z]*)", size_str)
if match:
val = float(match.group(1))
unit = match.group(2).lower()
if unit in ("gib", "gb"):
val *= 1024.0
elif unit in ("kib", "kb"):
val /= 1024.0
elif unit == "b":
val /= (1024.0 * 1024.0)
# Note: mib/mb units require no conversion as the accumulator is in MiB
total_mib += val|
|
||
| monkeypatch.setattr(hardware.subprocess, "run", fake_run) | ||
|
|
||
| assert hardware.detect_local_gpu() == ("Intel(R) Arc(TM) A770 Graphics", 16.0, 1) |
There was a problem hiding this comment.
⚪ LOW RISK
Nitpick: While 'assert' is standard practice in pytest, it is being flagged by the security linter (Bandit B101). To satisfy the linter without changing the test logic, you can add a '# nosec' comment to the line.
assert hardware.detect_local_gpu() == ("Intel(R) Arc(TM) A770 Graphics", 16.0, 1) # nosecThere was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
tests/test_hardware.py (1)
111-156: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd one malformed
xpu-smiregression case.These tests cover the happy paths, but not the case where
Device Name:parses andMemory Physical Size:does not. Once the parser guard is fixed, add a test that expects fallback instead of(intel_name, 0.0, 1)so this doesn't regress silently.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/test_hardware.py` around lines 111 - 156, The Intel GPU detection tests in detect_local_gpu only cover valid xpu-smi output, but not the malformed case where Device Name parses while Memory Physical Size fails. Add a regression test alongside test_detect_local_gpu_falls_back_to_xpu_smi and test_detect_local_gpu_sums_multiple_intel_gpus that stubs subprocess.run for xpu-smi discovery with an invalid memory value and asserts detect_local_gpu falls back instead of returning an Intel name with 0.0 memory and 1 device. Use the detect_local_gpu helper and the existing completed/fake_run pattern so the parser guard stays covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/modelinfo/hardware.py`:
- Around line 230-262: The Intel GPU detection path in the hardware parsing
logic is treating any collected `gpu_names` as a successful match even when
`Memory Physical Size:` was never parsed, which can return a bogus zero-VRAM
result. Update the parsing flow in the hardware info function so it only returns
from this branch when VRAM parsing succeeded, and otherwise falls through to the
non-Intel fallback; use the existing GPU parsing symbols like `gpu_names`,
`total_mib`, and the Intel display-name construction to locate the change.
---
Nitpick comments:
In `@tests/test_hardware.py`:
- Around line 111-156: The Intel GPU detection tests in detect_local_gpu only
cover valid xpu-smi output, but not the malformed case where Device Name parses
while Memory Physical Size fails. Add a regression test alongside
test_detect_local_gpu_falls_back_to_xpu_smi and
test_detect_local_gpu_sums_multiple_intel_gpus that stubs subprocess.run for
xpu-smi discovery with an invalid memory value and asserts detect_local_gpu
falls back instead of returning an Intel name with 0.0 memory and 1 device. Use
the detect_local_gpu helper and the existing completed/fake_run pattern so the
parser guard stays covered.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 1d93d2ca-b1c4-48eb-b532-f447d6cf1c4f
📒 Files selected for processing (2)
src/modelinfo/hardware.pytests/test_hardware.py
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/modelinfo/hardware.py (1)
233-265: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winRequire memory for every parsed Intel GPU.
Line 259 still treats any non-zero total as success. If
xpu-smireports twoDevice Name:rows but only one parseableMemory Physical Size:, this returnsgpu_count=2with one GPU’s VRAM, and the CLI consumes those values for capacity analysis. Track parsed memory entries and require them to match the parsed device count.Proposed fix
gpu_names: list[str] = [] total_mib: float = 0.0 + parsed_memory_entries = 0 for line in result.stdout.splitlines(): lower_line = line.lower() if "device name:" in lower_line: @@ val /= 1024.0 elif unit == "b": val /= (1024.0 * 1024.0) total_mib += val + parsed_memory_entries += 1 - if gpu_names and total_mib > 0.0: + if gpu_names and parsed_memory_entries == len(gpu_names) and total_mib > 0.0: gpu_count = len(gpu_names)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/modelinfo/hardware.py` around lines 233 - 265, The Intel GPU parsing in hardware.py still accepts a non-zero total memory even when not every parsed Device Name has a corresponding parseable Memory Physical Size. Update the Intel xpu-smi parsing block in the hardware detection logic to track how many memory rows were successfully parsed and only return from the GPU branch when that count matches the number of collected gpu_names; otherwise fall through so incomplete GPU capacity data is not reported. Use the existing symbols gpu_names, total_mib, and the Intel Multi-GPU return path to locate and adjust the check.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/test_hardware.py`:
- Around line 170-179: Bind the loop variable used by fake_run so Ruff B023 is
satisfied: the closure in fake_run currently captures size_str from the
surrounding loop, so update fake_run to take size_str as a defaulted parameter
and keep the existing xpu-smi discovery behavior unchanged. This applies in the
test_hardware helper around fake_run and the size_str-driven stdout
construction.
---
Duplicate comments:
In `@src/modelinfo/hardware.py`:
- Around line 233-265: The Intel GPU parsing in hardware.py still accepts a
non-zero total memory even when not every parsed Device Name has a corresponding
parseable Memory Physical Size. Update the Intel xpu-smi parsing block in the
hardware detection logic to track how many memory rows were successfully parsed
and only return from the GPU branch when that count matches the number of
collected gpu_names; otherwise fall through so incomplete GPU capacity data is
not reported. Use the existing symbols gpu_names, total_mib, and the Intel
Multi-GPU return path to locate and adjust the check.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 4de6be92-dbe8-4a9a-9c9e-15c83b7d36b6
📒 Files selected for processing (2)
src/modelinfo/hardware.pytests/test_hardware.py
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/modelinfo/hardware.py (1)
288-312: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winRestore the Apple Silicon fallback.
The supplied downstream test still expects
detect_local_gpu()to callsysctl hw.memsizeand return the Apple unified-memory tuple after NVIDIA/AMD/Intel detection fails. ReturningNonehere makes Apple--gpu autofall through to("Unknown", 8.0, 1)instead.Proposed fix
def _detect_apple_gpu() -> Optional[Tuple[str, float, int]]: - return None + try: + result = subprocess.run( + ["sysctl", "hw.memsize"], + capture_output=True, + text=True, + check=True, + timeout=2.0, + ) + mem_bytes = int(result.stdout.split(":", 1)[1].strip()) + mem_gb = mem_bytes / (1024**3) + return "Apple Silicon (Unified Memory)", max(mem_gb - 4.0, 0.0), 1 + except Exception: + return None🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/modelinfo/hardware.py` around lines 288 - 312, Restore the Apple Silicon fallback in detect_local_gpu() so that after _detect_nvidia_gpu(), _detect_amd_gpu(), and _detect_intel_gpu() all return None, the function still calls _detect_apple_gpu() and returns its unified-memory tuple when available; do not let the flow skip directly to the "Unknown" default. Locate the fix in detect_local_gpu() and keep the Apple-specific detection path intact so Apple --gpu auto continues to resolve via sysctl hw.memsize rather than falling through to the generic fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@src/modelinfo/hardware.py`:
- Around line 288-312: Restore the Apple Silicon fallback in detect_local_gpu()
so that after _detect_nvidia_gpu(), _detect_amd_gpu(), and _detect_intel_gpu()
all return None, the function still calls _detect_apple_gpu() and returns its
unified-memory tuple when available; do not let the flow skip directly to the
"Unknown" default. Locate the fix in detect_local_gpu() and keep the
Apple-specific detection path intact so Apple --gpu auto continues to resolve
via sysctl hw.memsize rather than falling through to the generic fallback.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: f3e66644-83ad-4ff0-bb8b-d9a8fc914a61
📒 Files selected for processing (2)
src/modelinfo/hardware.pytests/test_hardware.py
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/test_hardware.py
Summary
This pull request implements auto-detection for Intel GPUs (such as Intel Arc or Data Center GPUs) using
xpu-smi.Motivation & Context
Intel GPUs were previously absent from the hardware discovery pipeline, which only checked for NVIDIA (
nvidia-smi), AMD (rocm-smi), and Apple Silicon (sysctl). By addingxpu-smito the discovery flow, local hardware limits can be detected dynamically when using the--gpu autoflag, enabling correct mapping to the Intel configurations inKNOWN_GPUS.Type of Change
How Has This Been Tested?
The changes were validated using unit tests that mock the output of
xpu-smi discoveryto verify that both single-GPU and multi-GPU setups parse correctly and aggregate their VRAM capacities.Checklist
Summary by CodeRabbit
New Features
xpu-smi→ Apple Silicon), including per-device Intel naming and aggregated VRAM totals.Bug Fixes
xpu-smiparsing and unit conversion for “Memory Physical Size” (GiB/GB/KiB/KB and byte variants) to produce consistent VRAM values.xpu-smioutput, reliably falling back to the default “Unknown” result.Tests